Skip to content

Conversation

@philnik777
Copy link
Contributor

@philnik777 philnik777 commented Nov 4, 2025

Allocator copy/move operations can't throw according to [allocator.requirements], so we can just assume that they don't instead of checking. This removes a bunch of conditional noexcept specifications and simplifies even more.

@philnik777 philnik777 force-pushed the simplify_noexcept_specs branch from 8d3f860 to a757d96 Compare November 5, 2025 08:59
@github-actions
Copy link

github-actions bot commented Nov 5, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff origin/main HEAD --extensions ,cpp,h -- libcxx/test/libcxx/containers/sequences/vector.bool/move_noexcept.compile.pass.cpp libcxx/include/__hash_table libcxx/include/__memory/allocator_traits.h libcxx/include/__split_buffer libcxx/include/__tree libcxx/include/__vector/vector.h libcxx/include/__vector/vector_bool.h libcxx/include/deque libcxx/include/forward_list libcxx/include/list libcxx/include/queue libcxx/include/stack libcxx/include/string libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_noexcept.pass.cpp libcxx/test/std/containers/container.adaptors/flat.multimap/flat.multimap.cons/move_noexcept.pass.cpp libcxx/test/std/containers/container.adaptors/flat.multiset/flat.multiset.cons/move.pass.cpp libcxx/test/std/containers/container.adaptors/flat.set/flat.set.cons/move.pass.cpp libcxx/test/std/containers/sequences/deque/deque.special/swap_noexcept.pass.cpp libcxx/test/std/containers/sequences/forwardlist/forwardlist.spec/swap_noexcept.compile.pass.cpp libcxx/test/std/containers/sequences/list/list.special/swap_noexcept.pass.cpp libcxx/test/std/containers/sequences/vector.bool/swap_noexcept.pass.cpp libcxx/test/std/containers/sequences/vector/vector.cons/move_noexcept.pass.cpp libcxx/test/std/strings/basic.string/string.cons/move_noexcept.pass.cpp libcxx/test/libcxx/containers/sequences/deque/deque.cons/move_noexcept.compile.pass.cpp libcxx/test/libcxx/containers/sequences/forwardlist/move_noexcept.compile.pass.cpp libcxx/test/libcxx/containers/sequences/list/list.cons/move_noexcept.compile.pass.cpp --diff_from_common_commit

⚠️
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing origin/main to the base branch/commit you want to compare against.
⚠️

View the diff from clang-format here.
diff --git a/libcxx/test/std/containers/container.adaptors/flat.set/flat.set.cons/move.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.set/flat.set.cons/move.pass.cpp
index 5ffab4f0c..305736c88 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.set/flat.set.cons/move.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.set/flat.set.cons/move.pass.cpp
@@ -85,7 +85,7 @@ constexpr void test() {
 template <class T>
 struct PotentiallyThrowingMoveAllocator {
   using value_type                                    = T;
-  explicit PotentiallyThrowingMoveAllocator()                    = default;
+  explicit PotentiallyThrowingMoveAllocator()                               = default;
   PotentiallyThrowingMoveAllocator(const PotentiallyThrowingMoveAllocator&) = default;
   constexpr PotentiallyThrowingMoveAllocator(PotentiallyThrowingMoveAllocator&&) noexcept(false) {}
   constexpr T* allocate(std::ptrdiff_t n) { return std::allocator<T>().allocate(n); }

Copy link
Contributor

@frederick-vs-ja frederick-vs-ja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other failures seem to be from aggressive assertion on non-noexcept status.

Perhaps we shouldn't write such static_assert at least in libcxx/test/std unless the function is required to throw an exception under certain conditions.

@philnik777 philnik777 force-pushed the simplify_noexcept_specs branch 2 times, most recently from 7521604 to 3d12d11 Compare November 12, 2025 15:02
@philnik777 philnik777 force-pushed the simplify_noexcept_specs branch from 3d12d11 to 3bd634d Compare November 17, 2025 15:00
@ldionne ldionne marked this pull request as ready for review November 17, 2025 16:09
@ldionne ldionne requested a review from a team as a code owner November 17, 2025 16:09
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Nov 17, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 17, 2025

@llvm/pr-subscribers-libcxx

Author: Nikolas Klauser (philnik777)

Changes

Allocator copy/move operations can't throw according to [allocator.requiremetns], so we can just assume that they don't instead of checking. This removes a bunch of conditional noexcept specifications and simplifies even more.


Patch is 82.59 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/166397.diff

27 Files Affected:

  • (modified) libcxx/include/__hash_table (+15-39)
  • (modified) libcxx/include/__memory/noexcept_move_assign_container.h (-3)
  • (modified) libcxx/include/__split_buffer (+11-26)
  • (modified) libcxx/include/__tree (+10-29)
  • (modified) libcxx/include/__vector/vector.h (+10-44)
  • (modified) libcxx/include/__vector/vector_bool.h (+15-48)
  • (modified) libcxx/include/deque (+11-36)
  • (modified) libcxx/include/forward_list (+17-46)
  • (modified) libcxx/include/list (+15-44)
  • (modified) libcxx/include/queue (+1)
  • (modified) libcxx/include/stack (+1)
  • (modified) libcxx/include/string (+12-36)
  • (renamed) libcxx/test/libcxx/containers/sequences/deque/deque.cons/move_noexcept.compile.pass.cpp (+5-24)
  • (renamed) libcxx/test/libcxx/containers/sequences/forwardlist/move_noexcept.compile.pass.cpp (+5-24)
  • (renamed) libcxx/test/libcxx/containers/sequences/list/list.cons/move_noexcept.compile.pass.cpp (+4-22)
  • (added) libcxx/test/libcxx/containers/sequences/vector.bool/move_noexcept.compile.pass.cpp (+32)
  • (modified) libcxx/test/std/containers/container.adaptors/flat.map/flat.map.cons/move_noexcept.pass.cpp (-29)
  • (modified) libcxx/test/std/containers/container.adaptors/flat.multimap/flat.multimap.cons/move_noexcept.pass.cpp (-31)
  • (modified) libcxx/test/std/containers/container.adaptors/flat.multiset/flat.multiset.cons/move.pass.cpp (-21)
  • (modified) libcxx/test/std/containers/container.adaptors/flat.set/flat.set.cons/move.pass.cpp (+8-8)
  • (modified) libcxx/test/std/containers/sequences/deque/deque.special/swap_noexcept.pass.cpp (+2-4)
  • (modified) libcxx/test/std/containers/sequences/forwardlist/forwardlist.spec/swap_noexcept.compile.pass.cpp (+2-4)
  • (modified) libcxx/test/std/containers/sequences/list/list.special/swap_noexcept.pass.cpp (+2-4)
  • (removed) libcxx/test/std/containers/sequences/vector.bool/move_noexcept.pass.cpp (-59)
  • (modified) libcxx/test/std/containers/sequences/vector.bool/swap_noexcept.pass.cpp (-11)
  • (modified) libcxx/test/std/containers/sequences/vector/vector.cons/move_noexcept.pass.cpp (+1-4)
  • (modified) libcxx/test/std/strings/basic.string/string.cons/move_noexcept.pass.cpp (+1-3)
diff --git a/libcxx/include/__hash_table b/libcxx/include/__hash_table
index e1897949a47e6..6c895ab41b7af 100644
--- a/libcxx/include/__hash_table
+++ b/libcxx/include/__hash_table
@@ -518,13 +518,13 @@ public:
   _LIBCPP_HIDE_FROM_ABI __bucket_list_deallocator() _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
       : __size_(0) {}
 
-  _LIBCPP_HIDE_FROM_ABI __bucket_list_deallocator(const allocator_type& __a, size_type __size)
-      _NOEXCEPT_(is_nothrow_copy_constructible<allocator_type>::value)
-      : __size_(__size), __alloc_(__a) {}
+  _LIBCPP_HIDE_FROM_ABI __bucket_list_deallocator(const allocator_type& __a, size_type __size) _NOEXCEPT
+      : __size_(__size),
+        __alloc_(__a) {}
 
-  _LIBCPP_HIDE_FROM_ABI __bucket_list_deallocator(__bucket_list_deallocator&& __x)
-      _NOEXCEPT_(is_nothrow_move_constructible<allocator_type>::value)
-      : __size_(std::move(__x.__size_)), __alloc_(std::move(__x.__alloc_)) {
+  _LIBCPP_HIDE_FROM_ABI __bucket_list_deallocator(__bucket_list_deallocator&& __x) _NOEXCEPT
+      : __size_(std::move(__x.__size_)),
+        __alloc_(std::move(__x.__alloc_)) {
     __x.size() = 0;
   }
 
@@ -740,16 +740,14 @@ public:
   _LIBCPP_HIDE_FROM_ABI __hash_table(const __hash_table& __u, const allocator_type& __a);
   _LIBCPP_HIDE_FROM_ABI __hash_table(__hash_table&& __u) _NOEXCEPT_(
       is_nothrow_move_constructible<__bucket_list>::value&& is_nothrow_move_constructible<__first_node>::value&&
-          is_nothrow_move_constructible<__node_allocator>::value&& is_nothrow_move_constructible<hasher>::value&&
-              is_nothrow_move_constructible<key_equal>::value);
+          is_nothrow_move_constructible<hasher>::value&& is_nothrow_move_constructible<key_equal>::value);
   _LIBCPP_HIDE_FROM_ABI __hash_table(__hash_table&& __u, const allocator_type& __a);
   _LIBCPP_HIDE_FROM_ABI ~__hash_table();
 
   _LIBCPP_HIDE_FROM_ABI __hash_table& operator=(const __hash_table& __u);
   _LIBCPP_HIDE_FROM_ABI __hash_table& operator=(__hash_table&& __u)
       _NOEXCEPT_(is_nothrow_move_assignable<hasher>::value&& is_nothrow_move_assignable<key_equal>::value &&
-                 ((__node_traits::propagate_on_container_move_assignment::value &&
-                   is_nothrow_move_assignable<__node_allocator>::value) ||
+                 (__node_traits::propagate_on_container_move_assignment::value ||
                   allocator_traits<__node_allocator>::is_always_equal::value));
   template <class _InputIterator>
   _LIBCPP_HIDE_FROM_ABI void __assign_unique(_InputIterator __first, _InputIterator __last);
@@ -944,14 +942,7 @@ public:
   _LIBCPP_HIDE_FROM_ABI pair<const_iterator, const_iterator> __equal_range_multi(const _Key& __k) const;
 
   _LIBCPP_HIDE_FROM_ABI void swap(__hash_table& __u)
-#if _LIBCPP_STD_VER <= 11
-      _NOEXCEPT_(__is_nothrow_swappable_v<hasher>&& __is_nothrow_swappable_v<key_equal> &&
-                 (!allocator_traits<__pointer_allocator>::propagate_on_container_swap::value ||
-                  __is_nothrow_swappable_v<__pointer_allocator>) &&
-                 (!__node_traits::propagate_on_container_swap::value || __is_nothrow_swappable_v<__node_allocator>));
-#else
       _NOEXCEPT_(__is_nothrow_swappable_v<hasher>&& __is_nothrow_swappable_v<key_equal>);
-#endif
 
   _LIBCPP_HIDE_FROM_ABI size_type max_bucket_count() const _NOEXCEPT { return max_size(); }
   _LIBCPP_HIDE_FROM_ABI size_type bucket_size(size_type __n) const;
@@ -1011,15 +1002,11 @@ private:
 
   _LIBCPP_HIDE_FROM_ABI void __move_assign(__hash_table& __u, false_type);
   _LIBCPP_HIDE_FROM_ABI void __move_assign(__hash_table& __u, true_type)
-      _NOEXCEPT_(is_nothrow_move_assignable<__node_allocator>::value&& is_nothrow_move_assignable<hasher>::value&&
-                     is_nothrow_move_assignable<key_equal>::value);
-  _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__hash_table& __u) _NOEXCEPT_(
-      !__node_traits::propagate_on_container_move_assignment::value ||
-      (is_nothrow_move_assignable<__pointer_allocator>::value && is_nothrow_move_assignable<__node_allocator>::value)) {
+      _NOEXCEPT_(is_nothrow_move_assignable<hasher>::value&& is_nothrow_move_assignable<key_equal>::value);
+  _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__hash_table& __u) _NOEXCEPT {
     __move_assign_alloc(__u, integral_constant<bool, __node_traits::propagate_on_container_move_assignment::value>());
   }
-  _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__hash_table& __u, true_type) _NOEXCEPT_(
-      is_nothrow_move_assignable<__pointer_allocator>::value&& is_nothrow_move_assignable<__node_allocator>::value) {
+  _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__hash_table& __u, true_type) _NOEXCEPT {
     __bucket_list_.get_deleter().__alloc() = std::move(__u.__bucket_list_.get_deleter().__alloc());
     __node_alloc()                         = std::move(__u.__node_alloc());
   }
@@ -1132,8 +1119,7 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::__hash_table(const __hash_table& __u,
 template <class _Tp, class _Hash, class _Equal, class _Alloc>
 __hash_table<_Tp, _Hash, _Equal, _Alloc>::__hash_table(__hash_table&& __u) _NOEXCEPT_(
     is_nothrow_move_constructible<__bucket_list>::value&& is_nothrow_move_constructible<__first_node>::value&&
-        is_nothrow_move_constructible<__node_allocator>::value&& is_nothrow_move_constructible<hasher>::value&&
-            is_nothrow_move_constructible<key_equal>::value)
+        is_nothrow_move_constructible<hasher>::value&& is_nothrow_move_constructible<key_equal>::value)
     : __bucket_list_(std::move(__u.__bucket_list_)),
       __first_node_(std::move(__u.__first_node_)),
       __node_alloc_(std::move(__u.__node_alloc_)),
@@ -1278,8 +1264,7 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::__detach() _NOEXCEPT {
 
 template <class _Tp, class _Hash, class _Equal, class _Alloc>
 void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__move_assign(__hash_table& __u, true_type)
-    _NOEXCEPT_(is_nothrow_move_assignable<__node_allocator>::value&& is_nothrow_move_assignable<hasher>::value&&
-                   is_nothrow_move_assignable<key_equal>::value) {
+    _NOEXCEPT_(is_nothrow_move_assignable<hasher>::value&& is_nothrow_move_assignable<key_equal>::value) {
   clear();
   __bucket_list_.reset(__u.__bucket_list_.release());
   __bucket_list_.get_deleter().size()     = __u.__bucket_list_.get_deleter().size();
@@ -1325,8 +1310,7 @@ void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__move_assign(__hash_table& __u,
 template <class _Tp, class _Hash, class _Equal, class _Alloc>
 inline __hash_table<_Tp, _Hash, _Equal, _Alloc>& __hash_table<_Tp, _Hash, _Equal, _Alloc>::operator=(__hash_table&& __u)
     _NOEXCEPT_(is_nothrow_move_assignable<hasher>::value&& is_nothrow_move_assignable<key_equal>::value &&
-               ((__node_traits::propagate_on_container_move_assignment::value &&
-                 is_nothrow_move_assignable<__node_allocator>::value) ||
+               (__node_traits::propagate_on_container_move_assignment::value ||
                 allocator_traits<__node_allocator>::is_always_equal::value)) {
   __move_assign(__u, integral_constant<bool, __node_traits::propagate_on_container_move_assignment::value>());
   return *this;
@@ -2058,15 +2042,7 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::__equal_range_multi(const _Key& __k) c
 
 template <class _Tp, class _Hash, class _Equal, class _Alloc>
 void __hash_table<_Tp, _Hash, _Equal, _Alloc>::swap(__hash_table& __u)
-#if _LIBCPP_STD_VER <= 11
-    _NOEXCEPT_(__is_nothrow_swappable_v<hasher>&& __is_nothrow_swappable_v<key_equal> &&
-               (!allocator_traits<__pointer_allocator>::propagate_on_container_swap::value ||
-                __is_nothrow_swappable_v<__pointer_allocator>) &&
-               (!__node_traits::propagate_on_container_swap::value || __is_nothrow_swappable_v<__node_allocator>))
-#else
-    _NOEXCEPT_(__is_nothrow_swappable_v<hasher>&& __is_nothrow_swappable_v<key_equal>)
-#endif
-{
+    _NOEXCEPT_(__is_nothrow_swappable_v<hasher>&& __is_nothrow_swappable_v<key_equal>) {
   _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(
       __node_traits::propagate_on_container_swap::value || this->__node_alloc() == __u.__node_alloc(),
       "unordered container::swap: Either propagate_on_container_swap "
diff --git a/libcxx/include/__memory/noexcept_move_assign_container.h b/libcxx/include/__memory/noexcept_move_assign_container.h
index b0063516aaafc..e752d46496227 100644
--- a/libcxx/include/__memory/noexcept_move_assign_container.h
+++ b/libcxx/include/__memory/noexcept_move_assign_container.h
@@ -12,7 +12,6 @@
 #include <__config>
 #include <__memory/allocator_traits.h>
 #include <__type_traits/integral_constant.h>
-#include <__type_traits/is_nothrow_assignable.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -26,8 +25,6 @@ struct __noexcept_move_assign_container
                                _Traits::propagate_on_container_move_assignment::value
 #if _LIBCPP_STD_VER >= 17
                                    || _Traits::is_always_equal::value
-#else
-                                   && is_nothrow_move_assignable<_Alloc>::value
 #endif
                                > {
 };
diff --git a/libcxx/include/__split_buffer b/libcxx/include/__split_buffer
index 1e05e4df8ba0f..e25a4cc997577 100644
--- a/libcxx/include/__split_buffer
+++ b/libcxx/include/__split_buffer
@@ -28,8 +28,6 @@
 #include <__type_traits/conditional.h>
 #include <__type_traits/enable_if.h>
 #include <__type_traits/integral_constant.h>
-#include <__type_traits/is_nothrow_assignable.h>
-#include <__type_traits/is_nothrow_constructible.h>
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/is_trivially_destructible.h>
 #include <__type_traits/is_trivially_relocatable.h>
@@ -184,8 +182,7 @@ public:
   }
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void
-  __copy_without_alloc(__split_buffer_pointer_layout const& __other)
-      _NOEXCEPT_(is_nothrow_copy_assignable<pointer>::value) {
+  __copy_without_alloc(__split_buffer_pointer_layout const& __other) _NOEXCEPT {
     __front_cap_ = __other.__front_cap_;
     __begin_     = __other.__begin_;
     __end_       = __other.__end_;
@@ -341,8 +338,7 @@ public:
   }
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void
-  __copy_without_alloc(__split_buffer_size_layout const& __other)
-      _NOEXCEPT_(is_nothrow_copy_assignable<pointer>::value) {
+  __copy_without_alloc(__split_buffer_size_layout const& __other) _NOEXCEPT {
     __front_cap_ = __other.__front_cap_;
     __begin_     = __other.__begin_;
     __cap_       = __other.__cap_;
@@ -497,15 +493,11 @@ public:
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI
   __split_buffer(size_type __cap, size_type __start, __alloc_rr& __a);
 
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __split_buffer(__split_buffer&& __c)
-      _NOEXCEPT_(is_nothrow_move_constructible<allocator_type>::value);
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __split_buffer(__split_buffer&& __c) _NOEXCEPT;
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __split_buffer(__split_buffer&& __c, const __alloc_rr& __a);
 
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __split_buffer& operator=(__split_buffer&& __c)
-      _NOEXCEPT_((__alloc_traits::propagate_on_container_move_assignment::value &&
-                  is_nothrow_move_assignable<allocator_type>::value) ||
-                 !__alloc_traits::propagate_on_container_move_assignment::value);
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __split_buffer& operator=(__split_buffer&& __c) _NOEXCEPT;
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI ~__split_buffer();
 
@@ -559,8 +551,7 @@ public:
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __destruct_at_end(pointer __new_last, false_type) _NOEXCEPT;
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __destruct_at_end(pointer __new_last, true_type) _NOEXCEPT;
 
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void swap(__split_buffer& __x)
-      _NOEXCEPT_(!__alloc_traits::propagate_on_container_swap::value || __is_nothrow_swappable_v<__alloc_rr>);
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void swap(__split_buffer& __x) _NOEXCEPT;
 
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __invariants() const {
     if (__front_cap() == nullptr) {
@@ -594,8 +585,8 @@ public:
   }
 
 private:
-  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__split_buffer& __c, true_type)
-      _NOEXCEPT_(is_nothrow_move_assignable<allocator_type>::value) {
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void
+  __move_assign_alloc(__split_buffer& __c, true_type) _NOEXCEPT {
     __get_allocator() = std::move(__c.__get_allocator());
   }
 
@@ -740,8 +731,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 __split_buffer<_Tp, _Allocator, _Layout>::~__split
 }
 
 template <class _Tp, class _Allocator, template <class, class, class> class _Layout>
-_LIBCPP_CONSTEXPR_SINCE_CXX20 __split_buffer<_Tp, _Allocator, _Layout>::__split_buffer(__split_buffer&& __c)
-    _NOEXCEPT_(is_nothrow_move_constructible<allocator_type>::value)
+_LIBCPP_CONSTEXPR_SINCE_CXX20 __split_buffer<_Tp, _Allocator, _Layout>::__split_buffer(__split_buffer&& __c) _NOEXCEPT
     : __base_type(std::move(__c)) {
   __c.__reset();
 }
@@ -767,10 +757,7 @@ __split_buffer<_Tp, _Allocator, _Layout>::__split_buffer(__split_buffer&& __c, c
 
 template <class _Tp, class _Allocator, template <class, class, class> class _Layout>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 __split_buffer<_Tp, _Allocator, _Layout>&
-__split_buffer<_Tp, _Allocator, _Layout>::operator=(__split_buffer&& __c)
-    _NOEXCEPT_((__alloc_traits::propagate_on_container_move_assignment::value &&
-                is_nothrow_move_assignable<allocator_type>::value) ||
-               !__alloc_traits::propagate_on_container_move_assignment::value) {
+__split_buffer<_Tp, _Allocator, _Layout>::operator=(__split_buffer&& __c) _NOEXCEPT {
   clear();
   shrink_to_fit();
   __copy_without_alloc(__c);
@@ -780,8 +767,7 @@ __split_buffer<_Tp, _Allocator, _Layout>::operator=(__split_buffer&& __c)
 }
 
 template <class _Tp, class _Allocator, template <class, class, class> class _Layout>
-_LIBCPP_CONSTEXPR_SINCE_CXX20 void __split_buffer<_Tp, _Allocator, _Layout>::swap(__split_buffer& __x)
-    _NOEXCEPT_(!__alloc_traits::propagate_on_container_swap::value || __is_nothrow_swappable_v<__alloc_rr>) {
+_LIBCPP_CONSTEXPR_SINCE_CXX20 void __split_buffer<_Tp, _Allocator, _Layout>::swap(__split_buffer& __x) _NOEXCEPT {
   __base_type::swap(__x);
 }
 
@@ -852,8 +838,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void __split_buffer<_Tp, _Allocator, _Layout>::emp
 
 template <class _Tp, class _Allocator, template <class, class, class> class _Layout>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI void
-swap(__split_buffer<_Tp, _Allocator, _Layout>& __x, __split_buffer<_Tp, _Allocator, _Layout>& __y)
-    _NOEXCEPT_(_NOEXCEPT_(__x.swap(__y))) {
+swap(__split_buffer<_Tp, _Allocator, _Layout>& __x, __split_buffer<_Tp, _Allocator, _Layout>& __y) _NOEXCEPT {
   __x.swap(__y);
 }
 
diff --git a/libcxx/include/__tree b/libcxx/include/__tree
index ceae22bb48702..fedb31187d89b 100644
--- a/libcxx/include/__tree
+++ b/libcxx/include/__tree
@@ -904,14 +904,12 @@ public:
   _LIBCPP_HIDE_FROM_ABI void __assign_unique(_ForwardIterator __first, _ForwardIterator __last);
   template <class _InputIterator>
   _LIBCPP_HIDE_FROM_ABI void __assign_multi(_InputIterator __first, _InputIterator __last);
-  _LIBCPP_HIDE_FROM_ABI __tree(__tree&& __t) _NOEXCEPT_(
-      is_nothrow_move_constructible<__node_allocator>::value&& is_nothrow_move_constructible<value_compare>::value);
+  _LIBCPP_HIDE_FROM_ABI __tree(__tree&& __t) _NOEXCEPT_(is_nothrow_move_constructible<value_compare>::value);
   _LIBCPP_HIDE_FROM_ABI __tree(__tree&& __t, const allocator_type& __a);
 
   _LIBCPP_HIDE_FROM_ABI __tree& operator=(__tree&& __t)
       _NOEXCEPT_(is_nothrow_move_assignable<value_compare>::value &&
-                 ((__node_traits::propagate_on_container_move_assignment::value &&
-                   is_nothrow_move_assignable<__node_allocator>::value) ||
+                 (__node_traits::propagate_on_container_move_assignment::value ||
                   allocator_traits<__node_allocator>::is_always_equal::value)) {
     __move_assign(__t, integral_constant<bool, __node_traits::propagate_on_container_move_assignment::value>());
     return *this;
@@ -933,13 +931,7 @@ public:
 
   _LIBCPP_HIDE_FROM_ABI void clear() _NOEXCEPT;
 
-  _LIBCPP_HIDE_FROM_ABI void swap(__tree& __t)
-#if _LIBCPP_STD_VER <= 11
-      _NOEXCEPT_(__is_nothrow_swappable_v<value_compare> &&
-                 (!__node_traits::propagate_on_container_swap::value || __is_nothrow_swappable_v<__node_allocator>));
-#else
-      _NOEXCEPT_(__is_nothrow_swappable_v<value_compare>);
-#endif
+  _LIBCPP_HIDE_FROM_ABI void swap(__tree& __t) _NOEXCEPT_(__is_nothrow_swappable_v<value_compare>);
 
   template <class... _Args>
   _LIBCPP_HIDE_FROM_ABI iterator __emplace_multi(_Args&&... __args);
@@ -1281,17 +1273,14 @@ private:
   _LIBCPP_HIDDEN void destroy(__node_pointer __nd) _NOEXCEPT { (__tree_deleter(__node_alloc_))(__nd); }
 
   _LIBCPP_HIDE_FROM_ABI void __move_assign(__tree& __t, false_type);
-  _LIBCPP_HIDE_FROM_ABI void __move_assign(__tree& __t, true_type) _NOEXCEPT_(
-      is_nothrow_move_assignable<value_compare>::value&& is_nothrow_move_assignable<__node_allocator>::value);
+  _LIBCPP_HIDE_FROM_ABI void __move_assign(__tree& __t, true_type)
+      _NOEXCEPT_(is_nothrow_move_assignable<value_compare>::value);
 
-  _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__tree& __t)
-      _NOEXCEPT_(!__node_traits::propagate_on_container_move_assignment::value ||
-                 is_nothrow_move_assignable<__node_allocator>::value) {
+  _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__tree& __t) _NOEXCEPT {
     __move_assign_alloc(__t, integral_constant<bool, __node_traits::propagate_on_container_move_assignment::value>());
   }
 
-  _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__tree& __t, true_type)
-      _NOEXCEPT_(is_nothrow_move_assignable<__node_allocator>::value) {
+  _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__tree& __t, true_type) _NOEXCEPT {
     __node_alloc() = std::move(__t.__node_alloc());
   }
   _LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__tree&, false_type) _NOEXCEPT {}
@@ -1605,8 +1594,7 @@ __tree<_Tp, _Compare, _Allocator>::__tree(const __tree& __t)
 }
 
 template <class _Tp, class _Compare, class _Allocator>
-__tree<_Tp, _Compare, _Allocator>::__tree(__tree&& __t) _NOEXCEPT_(
-    is_nothrow_move_constructible<__node_allocator>::value&& is_nothrow_move_constructible<value_compare>::value)
+__tree<_Tp, _Compare, _Allocator>::__tree(__tree&& __t) _NOEXCEPT_(is_nothrow_move_constructible<value_compare>::value)
     : __begin_node_(std::move(__t.__begin_node_)),
       __end_node_(std::move(__t.__end_node_)),
       __node_alloc_(std::move(__t.__node_alloc_)),
@@ -1649,7 +1637,7 @@ __tree<_Tp, _Compare, _Allocator>::__tree(__tree&& __t, const allocator_type& __
 
 template <class _Tp, class _Compare, class _Allocator>
 void __tree<_Tp, _Compare, _Allocator>::__move_assign(__tree& __t, true_type)
-    _NOEXCEPT_(is_nothrow_move_assignable<value_compare>::value&& is_nothrow_move_assignable<__node_allocator>::value) {
+    _NOEXCEPT_(is_nothrow_move_assignable<value_compare>::value) {
   destroy(static_cast<__node_pointer>(__end_node()->__left_));
   __begin_node_ = __t.__begin_node_;
   __end_node_   = __t.__end_node_;
@@ -1687,14 +1675,7 @@ void __tree<_Tp, _Compare, _Allocator>::__move_assign(__tree& __t, false_type) {
 }
 
 template <class _Tp, class _Compare, class _Allocator>
-void __tree<_Tp, _Compare, _Allocator>::swap(__tree& __t)
-#if _LIBCPP_STD_VER <= 11
-    _NOEXCEPT_(__is_nothrow_swappable_v<value_compare> &&
-               (!__node_traits::propagate_on_container_swap::value || __is_nothrow_swappable_v<__node_allocator>))
-#else
-    _NOEXCEPT_(__is_nothrow_swappable_v<value_compare>)
-#endif
-{
+void __tree<_Tp, _Compare, _Allocator>::swap(__tree& __t) _NOEXCEPT_(__is_nothrow_swappable_v<value_compare>) {
   using std::swap;
   swap(__begin_node_, __t.__begin_node_);
   swap(__end_node_, __t.__end_node_);
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
ind...
[truncated]

Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can only guess, but I suspect the reason for having written these conditional noexcepts this way could be the fact that Clang generates sub-optimal code when calling a non-noexcept function from a noexcept function. It basically generates a try-catch that calls terminate in the catch block.

Hence, it's technically possible that we'd start generating worse code when we have an allocator that doesn't throw but fails to be marked as noexcept. Arguably, we should fix Clang's strategy for handling this as discussed in https://discourse.llvm.org/t/rfc-add-call-unwindabort-to-llvm-ir.

Generally speaking LGTM but I'd like you to investigate the situation for move-assignment operators.

@philnik777 philnik777 force-pushed the simplify_noexcept_specs branch from 3bd634d to 11fccc6 Compare November 19, 2025 14:51
is_nothrow_move_assignable<__node_allocator>::value) ||
allocator_traits<__node_allocator>::is_always_equal::value));
_NOEXCEPT_(is_nothrow_move_assignable<hasher>::value&& is_nothrow_move_assignable<key_equal>::value&&
__is_allocator_aware_container_move_nothrow_v<allocator_type>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a libc++ specific test for these properties that we were apparently not testing previously.

Or if we're already testing this somehow, there should be a test changing in std::unordered_map and friends.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants